<?php
/**
 * Created by PhpStorm.
 * Author: 芸签cms www.yunqiancms.com
 * User: sun
 * Date: 2022/8/22
 * Time: 4:17 PM
 */
namespace app\backend\modules\sys\services;

use app\common\exceptions\BizException;
use app\common\helpers\Cache;
use app\common\helpers\Utils;
use app\common\services\AutoUpdate;
use Illuminate\Filesystem\Filesystem;
use vierbergenlars\SemVer\version;
use app\common\facades\Setting;

class UpdateService
{

    private $autoUpdate;

    private $filesystem;

    private $downloadDomain;

    private $key;

    private $secret;

    public function __construct()
    {
        $setting = Setting::get('cms.auth');

        $this->key = $setting['key'];
        $this->secret = $setting['secret'];

        $this->autoUpdate = new AutoUpdate();
        $this->filesystem = app(Filesystem::class);

        $this->downloadDomain = config('auto-update.domain');
    }
    public function systemInfo()
    {
        //执行迁移文件
        $this->runMigrate();
        //清理缓存
        $this->clearCache();

        $result = $this->systemCheck();
        return $result;
    }

    public function log($page)
    {
        $res = $this->autoUpdate->showLog($page,$this->key,$this->secret);

        return $res['result'];
    }

    /**
     * 检测更新
     * @return \Illuminate\Http\JsonResponse
     */
    public function verifyCheck()
    {
        set_time_limit(0);
        $domain = request()->getHttpHost();
        //执行迁移文件
        $this->runMigrate();

        $filter_file = ['composer.json', 'README.md'];
        $plugins_dir = $this->autoUpdate->getDirsByPath('plugins', $this->filesystem);

        $result = ['result' => -2, 'msg' => '网络请求超时', 'version' => ''];

        if (!$this->key || !$this->secret) {
            return response()->json(['result' => -1, 'msg' => '系统未授权', 'data' => []]);
        }

        //前端更新文件检测 todo 暂时没有前端
        //$frontendUpgrad = $this->frontendUpgrad($this->key, $this->secret);

        //更新文件检测(后端、插件、模版等)
        $ret = $this->autoUpdate->backendCheck([
            'vendor'  => $this->autoUpdate->getDirsByPath('vendor'),
            'domain'  => $domain
        ],$this->key,$this->secret);

        if ($ret == 'unknown') {
            //throw new BizException();
            $result = ['result' => -1];
        }

        if (is_array($ret)) {
            if (!empty($ret['php-version']) && !$this->checkPHPVersion($ret['php-version'])) {
                $result = ['result' => 98, 'msg' => '服务器php版本(v' . PHP_VERSION . ')过低,不符合更新条件,建议升级到php版本>=(v' . $ret['php-version'] . ')', 'last_version' => ''];
                return $result;
            }

            if (1 == $ret['result']) {
                $files = [];

                if (!empty($ret['files'])) {
                    foreach ($ret['files'] as $file) {
                        //忽略指定文件
                        if (in_array($file['path'], $filter_file)) {
                            continue;
                        }

                        //忽略前端样式文件
                        if (preg_match('/^static\/app/', $file['path'])) {
                            continue;
                        }

                        //忽略没有安装的插件
                        if (preg_match('/^plugins/', $file['path'])) {
                            $sub_dir = substr($file['path'], strpos($file['path'], '/') + 1);
                            $sub_dir = substr($sub_dir, 0, strpos($sub_dir, '/'));

                            if (!in_array($sub_dir, $plugins_dir)) {
                                continue;
                            }
                        }
                        //忽略没有安装的模版
                        if (preg_match('/^themes/', $file['path'])) {
                            $sub_dir = substr($file['path'], strpos($file['path'], '/') + 1);
                            $sub_dir = substr($sub_dir, 0, strpos($sub_dir, '/'));

                            if (!in_array($sub_dir, $plugins_dir)) {
                                continue;
                            }
                        }
                        //忽略前后端版本号记录文件
                        if (($file['path'] == 'config/front-version.php'
                                || $file['path'] == 'config/backend_version.php')
                            && is_file(base_path() . '/' . $file['path'])) {
                            continue;
                        }

                        $entry = base_path() . '/' . $file['path'];

                        if ($file['path'] == 'composer.lock' && md5_file($entry) != $file['md5']) {
                            $this->setComposerStatus();
                        }

                        //如果本地没有此文件或者文件与服务器不一致
                        if (!is_file($entry) || md5_file($entry) != $file['md5']) {
                            $files[] = array(
                                'path' => $file['path'],
                                'download' => 0
                            );
                            $difffile[] = $file['path'];
                        } else {
                            $samefile[] = $file['path'];
                        }
                    }
                }

                $tmpdir = storage_path('app/public/tmp/' . date('ymd'));
                if (!is_dir($tmpdir)) {
                    $this->filesystem->makeDirectory($tmpdir, 0755, true);
                }

                $ret['files'] = $files;
                file_put_contents($tmpdir . "/file.txt", json_encode($ret));

                if (empty($files)) {
                    $version = config('version');
                    //TODO 更新日志记录
                } else {
                    $version = $ret['version'];
                }

                //$this->mvenv();todo 确认如何更新env

                $result = [
                    'result' => 1,
                    'version' => $version,
                    'files' => $ret['files'],
                    'filecount' => count($files),
                    //'frontendUpgrad' => count($frontendUpgrad),
                    //'list' => $frontendUpgrad
                ];
            } else {
                preg_match('/"[\d\.]+"/', file_get_contents(base_path('config/') . 'version.php'), $match);
                $version = $match ? trim($match[0], '"') : '1.0.0';

                $result = ['result' => 99, 'msg' => '', 'version' => $version];
            }
        }
        return $result;
    }
    public function fileDownload($nofiles)
    {
        $tmpdir = storage_path('app/public/tmp/' . date('ymd'));
        $f = file_get_contents($tmpdir . "/file.txt");
        $upgrade = json_decode($f, true);
        $files = $upgrade['files'];
        $total = count($upgrade['files']);
        $path = "";
        $status = 1;

        //找到一个没更新过的文件去更新
        foreach ($files as $f) {
            if (empty($f['download'])) {
                $path = $f['path'];
                break;
            }
        }

        if (!empty($path)) {
            if (!empty($nofiles)) {
                if (in_array($path, $nofiles)) {
                    foreach ($files as &$f) {
                        if ($f['path'] == $path) {
                            $f['download'] = 1;
                            break;
                        }
                    }
                    unset($f);
                    $upgrade['files'] = $files;
                    $tmpdir = storage_path('app/public/tmp/' . date('ymd'));
                    if (!is_dir($tmpdir)) {
                        $this->filesystem->makeDirectory($tmpdir, 0755, true);
                    }
                    file_put_contents($tmpdir . "/file.txt", json_encode($upgrade));
                    return ['result' => 3];
                }
            }

            //Check for a new download
            $ret = $this->autoUpdate->appBackDownload([
                'path' => urlencode($path)
            ],$this->key,$this->secret);

            $this->setSysUpgrade($ret);
            $this->setVendorZip($ret);

            //下载vendor
            if ($this->isVendorZip()) {
                $this->downloadVendorZip();
            }

            //预下载
            if (is_array($ret)) {
                $path = $ret['path'];
                $dirpath = dirname($path);
                $save_path = storage_path('app/auto-update/cms') . '/' . $dirpath;

                if (!is_dir($save_path)) {
                    $this->filesystem->makeDirectory($save_path, 0755, true);
                }

                //新建
                $content = base64_decode($ret['content']);
                file_put_contents(storage_path('app/auto-update/cms') . '/' . $path, $content);

                $success = 0;
                foreach ($files as &$f) {
                    if ($f['path'] == $path) {
                        $f['download'] = 1;
                        break;
                    }
                    if ($f['download']) {
                        $success++;
                    }
                }

                unset($f);
                $upgrade['files'] = $files;
                $tmpdir = storage_path('app/public/tmp/' . date('ymd'));

                if (!is_dir($tmpdir)) {
                    $this->filesystem->makeDirectory($tmpdir, 0755, true);
                }

                file_put_contents($tmpdir . "/file.txt", json_encode($upgrade));
            }
            return ['result' => 1];
        } else {
            //覆盖
            foreach ($files as $f) {
                $path = $f['path'];
                $file_dir = dirname($path);

                if (!is_dir(base_path($file_dir))) {
                    $this->filesystem->makeDirectory(base_path($file_dir), 0755, true);
                }

                $content = file_get_contents(storage_path('app/auto-update/cms') . '/' . $path);

                //去除空文件判断
                if (!empty($content)) {
                    file_put_contents(base_path($path), $content);

                    @unlink(storage_path('app/auto-update/cms') . '/' . $path);
                }
            }
            $status = 2;
            $success = $total;

            if ($this->isVendorZip() && $this->validateVendorZip()) {
                $this->delConfig();
                $this->renameVendor();
                $res = $this->unVendorZip();

                if (!$res) {
                    $this->renameVendor($res);
                }
            }

            //清理缓存
            $this->clearCache($this->systemUpgrade->filesystem);

            \Log::debug('----Queue Restarth----');
            //app('supervisor')->restart();
            //(new HostManager())->restart();

            if ($this->isVendorZip()) {
                if ($this->validateVendorZip()) {
                    $this->delVendor(base_path('vendor_' . date('Y-m-d')));
                }

                $this->delVendorZip();
            }

            $result = [
                'result' => 9,
                'total' => $total,
                'success' => $success
            ];
            return $result;
        }

    }

    /**
     * 执行迁移文件
     */
    public function runMigrate()
    {
        \Log::debug('----CLI----');
        \Artisan::call('migrate',['--force' => true]);
        $plugins_dir = $this->autoUpdate->getDirsByPath('plugins', $this->filesystem);
        if (!empty($plugins_dir)) {
            \Artisan::call('update:version', ['version' => $plugins_dir]);
        }
    }
    /**
     * 清理缓存
     */
    public function clearCache()
    {
        $request = request();
        \Artisan::call('config:cache');
        \Cache::flush();
        app()->instance('request', $request);
    }
    public function systemCheck()
    {
        $data = [
            'local_backend_version' => '未知',
            'local_frontend_version' => '未知',
            'origin_backend_version' => '未知',
            'origin_frontend_version' => '未知',
            'origin_domain' => '未知',
            'current_domain' => '未知',
            'domain_status_txt' => '未知',
            'origin_plugins_count' => '未知',
            'local_plugins_count' => '未知',
            'no_auth_plugins' => '未知',
            'upgrade_status' => '未知'
        ];

        $origin = $this->autoUpdate->systemCheck($this->key,$this->secret);

        if (!isset($origin['result'])) {
            return $data;
        }
        $ver = $this->compareVersion($origin);
        $domainInfo = $this->domainInfo($origin);
        $pluginsInfo = $this->pluginsInfo($origin);
        $upgrade = $this->systemStatus($domainInfo, $pluginsInfo);

        $data = [
            'local_backend_version' => $ver['localBackendVersion'],
            'local_frontend_version' => $ver['localFrontendVersion'],
            'origin_backend_version' => $ver['originBackendVersion'],
            'origin_frontend_version' => $ver['originFrontendVersion'],
            'hasUpgradeFile' => $ver['hasUpgradeFile'],
            'origin_domain' => $domainInfo['originDomain'],
            'current_domain' => $domainInfo['currentDomain'],
            'domain_status_txt' => $domainInfo['domainStatusTxt'],
            'origin_plugins_count' => $pluginsInfo['originAuthPlugins'],
            'local_plugins_count' => $pluginsInfo['localInstallPlugins'],
            'no_auth_plugins' => $pluginsInfo['localNoAuthPlugins'],
            'upgrade_status' => $upgrade['status'],
            'service_time' => $origin->result->remainingTime
        ];

        return $data;
    }
    /**
     * 版本比较
     *
     * @param $autoUpdate
     * @return array
     */
    public function compareVersion($params)
    {
        $originBackendVersion = '未知';
        $originFrontendVersion = '未知';
        $hasUpgradeFile = 0;


        if (isset($params['result'])) {
            $originBackendVersion = $params['result']['backendVersion'];
            $originFrontendVersion = $params['result']['frontendVersion'];
        }

        $localFrontendVersion = $this->getFrontendVersioin();
        $localBackendVersion = $this->getBackendVersion();

        if (version::eq($localFrontendVersion, $originFrontendVersion)) {
            $originFrontendVersion = '已经和本地版本一致';
        }

        if (version::eq($localBackendVersion, $originBackendVersion)) {
            $originBackendVersion = '已经和本地版本一致';
        }

        if ($originBackendVersion == '已经和本地版本一致' && $originFrontendVersion == '已经和本地版本一致') {
            $hasUpgradeFile = 1;
        }

        return [
            'localBackendVersion' => $localBackendVersion,
            'localFrontendVersion' => $localFrontendVersion,
            'originBackendVersion' => $originBackendVersion,
            'originFrontendVersion' => $originFrontendVersion,
            'hasUpgradeFile' => $hasUpgradeFile
        ];
    }
    /**
     * 获取本地前端版本
     *
     * @return \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed
     */
    public function getFrontendVersioin()
    {
        return config('front-version');
    }

    /**
     * 获取本地后端版本
     *
     * @return \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed
     */
    public function getBackendVersion()
    {
        return config('version');
    }
    /**
     * 域名信息
     *
     * @param $params
     * @return array
     */
    public function domainInfo($params)
    {
        $domainStatus = 0;
        $currentDomain = rtrim(request()->getHttpHost(), '/');

        $originDomain = '未知';

        if (isset($params['result'])) {
            $originDomain = $params['result']['authDomain'];
        }

        if (strpos($currentDomain, $originDomain) !== false) {
            $domainStatus = 1;
        }

        switch ($domainStatus) {
            case 0:
                $domainStatusTxt = '不一致';
                break;
            case 1:
                $domainStatusTxt = '已授权';
                break;
            default:
                $domainStatusTxt = '未知';
        }

        return [
            'originDomain' => $originDomain,
            'domainStatus' => $domainStatus,
            'domainStatusTxt' => $domainStatusTxt,
            'currentDomain' => $currentDomain
        ];
    }
    /**
     * 本地安装插件
     *
     * @return array
     */
    public function localPlugins()
    {
        $localPlugins = [];

        $resource = opendir(base_path('plugins'));

        while ($file_name = @readdir($resource)) {
            if ($file_name == '.' || $file_name == '..')
                continue;
            $plugin_path = base_path('plugins') . '/' . $file_name;
            if (is_dir($plugin_path)) {
                $localPlugins[] = $file_name;
            }
        }

        closedir($resource);

        return $localPlugins;
    }
    /**
     * 授权插件信息
     *
     * @param $params
     * @return array
     */
    public function originPlugins($params)
    {
        $originAuthPlugins = '未知';
        $originAllPlugins = [];
        $tmp_authPlugins = [];
        $tmp_allPlugins = [];
        $noAuthPlugins = [];  //未授权插件

        if (isset($params['result'])) {
            $originAuthPlugins = $params['result']['authPlugins'];
            $originAllPlugins = $params['result']['allPlugins'];
        }

        foreach ($originAuthPlugins as $items) {
            $tmp_authPlugins[] = $items['name'];
        }

        foreach ($originAllPlugins as $rows) {
            $tmp_allPlugins[] = $rows['name'];
        }

        $noAuthPlugins = array_diff($tmp_allPlugins, $tmp_authPlugins);

        if (is_array($originAuthPlugins)) {
            $originAuthPlugins = count($originAuthPlugins);
        }

        return [
            'originAuthPlugins' => $originAuthPlugins,
            'noAuthPlugins' => $noAuthPlugins
        ];
    }
    /**
     * 插件信息
     *
     * @param $params
     * @return array
     */
    public function pluginsInfo($params)
    {
        //忽略
        $filter_plugins = [];
        //未授权安装插件
        $localNoAuthPlugins = [];

        $originPlugins = $this->originPlugins($params);
        $localPlugins = $this->localPlugins();

        if (!empty($originPlugins['noAuthPlugins'])) {
            $localNoAuthPlugins = array_intersect($originPlugins['noAuthPlugins'], $localPlugins);
            $localNoAuthPlugins = array_diff($localNoAuthPlugins, $filter_plugins);
        }

        return [
            'localInstallPlugins' => count($localPlugins),
            'originAuthPlugins' => $originPlugins['originAuthPlugins'],
            'localNoAuthPlugins' => count($localNoAuthPlugins)
        ];
    }
    public function systemStatus($domainInfo, $pluginsInfo)
    {
        $buttonStatus = 0;  // 0-禁止更新;1-允许更新

        if ($domainInfo['domainStatus'] == 1 && $pluginsInfo['localNoAuthPlugins'] == 0) {
            $buttonStatus = 1;
        }

        return ['status' => $buttonStatus];
    }
    /**
     * 验证php版本
     *
     * @param $php_version
     * @return bool
     */
    public function checkPHPVersion($php_version)
    {
        if (version::lt($php_version, PHP_VERSION)) {
            return true;
        }

        return false;
    }
    /**
     * composer是否需要更新
     */
    public function setComposerStatus()
    {
        Cache::put('sys_composer_status', 1, 60);
    }
    /**
     * 迁移数据库信息
     * todo 确认
     */
    public function mvenv()
    {
        $database = config('database');
        $databaseSet = $database['connections'][$database['default']];

        $DB_HOST = $databaseSet['host'];
        $DB_USERNAME = $databaseSet['username'];
        $DB_PASSWORD = $databaseSet['password'];
        $DB_PORT = $databaseSet['port'];
        $DB_DATABASE = $databaseSet['database'];
        $DB_PREFIX = $databaseSet['prefix'];

            $str = '<?php
$config = array();

$config[\'db\'][\'master\'][\'host\'] = \'' . $DB_HOST . '\';
$config[\'db\'][\'master\'][\'username\'] = \'' . $DB_USERNAME . '\';
$config[\'db\'][\'master\'][\'password\'] = \'' . $DB_PASSWORD . '\';
$config[\'db\'][\'master\'][\'port\'] = \'' . $DB_PORT . '\';
$config[\'db\'][\'master\'][\'database\'] = \'' . $DB_DATABASE . '\';
$config[\'db\'][\'master\'][\'tablepre\'] = \'' . $DB_PREFIX . '\';

$config[\'db\'][\'slave_status\'] = false;
$config[\'db\'][\'slave\'][\'1\'][\'host\'] = \'\';
$config[\'db\'][\'slave\'][\'1\'][\'username\'] = \'\';
$config[\'db\'][\'slave\'][\'1\'][\'password\'] = \'\';
$config[\'db\'][\'slave\'][\'1\'][\'port\'] = \'\';
$config[\'db\'][\'slave\'][\'1\'][\'database\'] = \'\';
$config[\'db\'][\'slave\'][\'1\'][\'tablepre\'] = \'\';
';
            $this->filesystem->put(base_path('database/config.php'), $str);

            if (file_exists(base_path('database/config.php'))) {
                $str = "APP_ENV=production
APP_KEY=base64:2q7s0Z714xS1L1WNN/8dsB69XDqOb4Qdptgh4X2ZtZU=
APP_DEBUG=true
APP_LOG_LEVEL=debug

APP_Framework=platform
IS_WEB=/admin/shop
ROOT_PATH=''
EXTEND_DIR=''";
                $this->filesystem->put(base_path('.env'), $str);
            }
    }
    /**
     * 设置更新的系统版本分支
     *
     * @param $ret
     */
    public function setSysUpgrade($ret)
    {
        Cache::put('sys_upgrade', $ret['upgrade'], 60);
    }

    /**
     * 获取系统版本分支
     *
     * @return mixed|string
     */
    public function getSysUpgrade()
    {
        if (Cache::has('sys_upgrade')) {
            return Cache::get('sys_upgrade');
        }

        return 'vendor';
    }

    /**
     * 设置vendor压缩包是否下载成功
     * @param $ret
     */
    public function setVendorZip($ret)
    {
        Cache::put('sys_vendor_zip', $ret['is_vendor_zip'], 60);
    }

    /**
     * 本地是否存在vendor压缩包
     *
     * @return false|mixed
     */
    public function isVendorZip()
    {
        if (!$this->getComposerStatus()) {
            return false;
        }

        if (Cache::has('sys_vendor_zip')) {
            return Cache::get('sys_vendor_zip');
        }

        return false;
    }
    /**
     * 获取composer状态
     *
     * @return false|mixed
     */
    public function getComposerStatus()
    {
        if (Cache::has('sys_composer_status')) {
            return Cache::get('sys_composer_status');
        }

        return false;
    }
    /**
     * 下载venddor组件压缩包
     */
    public function downloadVendorZip()
    {
        $url = $this->downloadDomain.'/update_vendor/' . $this->getSysUpgrade() . '.zip';
        $autoUpdate = new AutoUpdate();
        $url = $url . "?token=" . $autoUpdate->getToken();

        $tmp_path = base_path($this->getSysUpgrade() . '_' . date('Y-m-d') . '.zip');

        if (file_exists($tmp_path)) {
            return;
        }

        try {
            Utils::download($url, $tmp_path);
            \Log::debug('----vendor zip 下载ok----');
        } catch (\Exception $e) {
            \Log::debug('----vendor zip 下载失败----');
        }
    }
    /**
     * 获取授权系统前端版本号
     *
     * @param $updateList
     * @return mixed
     */
    public function getFrontVersion($updateList)
    {
        rsort($updateList);
        $version = $updateList[0]['version'];

        return $version;
    }
    /**
     * 更新本地前/后端版本号
     *
     * @param $updateList
     */
    public function setSystemVersion($updateList, $type = 1)
    {
        $version = $this->getFrontVersion($updateList);

        $str = file_get_contents(base_path('config/') . 'front-version.php');
        $str = preg_replace('/"[\d\.]+"/', '"' . $version . '"', $str);

        switch ($type) {
            case 1:
                file_put_contents(base_path('config/') . 'front-version.php', $str);
                break;
            case 2:
                file_put_contents(base_path('config/') . 'backend_version.php', $str);
                break;
        }
    }
    /**
     * 检查vendor压缩包下载文件是否有效
     *
     * @return bool
     */
    public function validateVendorZip()
    {
        $path = base_path($this->getSysUpgrade() . '_' . date('Y-m-d') . '.zip');

        if (file_exists($path) && filesize($path) > 0) {
            \Log::debug('--------validateVendorZip------');
            return true;
        }
        \Log::debug('--------no validateVendorZip------');

        return false;
    }
    /**
     * 删除缓存配置文件
     */
    public function delConfig()
    {
        $path = base_path('bootstrap/cache/config.php');

        if (file_exists($path)) {
            @unlink($path);
            \Log::debug('----config 删除ok----');
        }
    }
    /**
     * 下载vendor组件包重命名
     *
     * @param bool $res
     */
    public function renameVendor($res = true)
    {
        \Log::debug('------renameVendor-------', [$res]);

        if ($res) {
            rename(base_path('vendor'), base_path('vendor_' . date('Y-m-d')));
        } else {
            rename(base_path('vendor_' . date('Y-m-d')), base_path('vendor'));
        }
    }
    /**
     * 解压vendor压缩包
     *
     * @return bool
     */
    public function unVendorZip()
    {
        ini_set("memory_limit", "-1"); //不限制内存
        ini_set('max_execution_time', '0');

        $path = base_path($this->getSysUpgrade() . '_' . date('Y-m-d') . '.zip');

        if (file_exists($path)) {
            $zip = new \ZipArchive();
            $res = $zip->open($path);

            if ($res === true) {
                try {
                    $zip->extractTo(base_path());
                } catch (\Exception $e) {
                    $zip->close();
                    \Log::debug('----vendor zip 解压失败----');
                    return false;
                }
            } else {
                $zip->close();
                \Log::debug('----vendor zip 下载失败----');
                return false;
            }
            $zip->close();

            \Log::debug('----vendor zip 解压ok----');
            return true;
        }
    }
    /**
     * 删除本地vendor组件包
     *
     * @param $path
     * @return bool
     */
    public function delVendor($path)
    {
        \Log::debug('------delVendor-------');

        if (is_dir($path)) {
            $p = scandir($path);
            if (count($p) > 2) {
                foreach ($p as $val) {
                    if ($val != "." && $val != "..") {
                        if (is_dir($path . '/' . $val)) {
                            $this->delVendor($path . '/' . $val . '/');
                        } else {
                            unlink($path . '/' . $val);
                        }
                    }
                }
            }
        }

        return rmdir($path);
    }
    /**
     * 删除vendor压缩包
     */
    public function delVendorZip()
    {
        $path = base_path($this->getSysUpgrade() . '_' . date('Y-m-d') . '.zip');

        if (file_exists($path)) {
            @unlink($path);
            \Log::debug('----vendor zip 删除ok----');
        }
    }
}